package mmc.resource.models.collada;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import org.collada.x2005.x11.colladaSchema.COLLADADocument;
import org.collada.x2005.x11.colladaSchema.InputLocal;
import org.collada.x2005.x11.colladaSchema.InputLocalOffset;
import org.collada.x2005.x11.colladaSchema.ControllerDocument.Controller;
import org.collada.x2005.x11.colladaSchema.InstanceControllerDocument.InstanceController;
import org.collada.x2005.x11.colladaSchema.LibraryControllersDocument.LibraryControllers;
import org.collada.x2005.x11.colladaSchema.LibraryVisualScenesDocument.LibraryVisualScenes;
import org.collada.x2005.x11.colladaSchema.NodeDocument.Node;
import org.collada.x2005.x11.colladaSchema.SkinDocument.Skin;
import org.collada.x2005.x11.colladaSchema.VisualSceneDocument.VisualScene;

import mmc.EngineObject;

public class ColladaSkinController {
	List<Joint> joints = new ArrayList<Joint>();
	Matrix4 bindShapeMatrix;
	Joint[] vertexWeightJoints; 
	float[] vertexWeights;
	int[] vertexWeightsOffset;
	ColladaNode[] skeletonRoots;
	ColladaDynamicGeometry geometry;
	
	@SuppressWarnings("unchecked")
	public ColladaSkinController(COLLADADocument doc, InstanceController ic){
		skeletonRoots = searchSkeletonRoot(doc, ic.getSkeletonArray());
		Controller c = loadController(doc, ic.getUrl());
		
		if(!c.isSetSkin())
			throw new RuntimeException("Currently supporting Skin-Controllers only!");
		
		Skin skin = c.getSkin();
		
		geometry = new ColladaDynamicGeometry(doc, skin.getSource2());
		
		if(!skin.isSetBindShapeMatrix())
			bindShapeMatrix = new Matrix4();
		else
			bindShapeMatrix = new Matrix4(skin.getBindShapeMatrix());
		
		//Load joints
		ColladaDataSource<String> jointIds = null;
		ColladaDataSource<Matrix4> invBindmatrixSource = null;
		
		for(InputLocal i:skin.getJoints().getInputArray()){
			if(i.getSemantic().equals("JOINT")){
				jointIds = new ColladaDataSource<String>(skin, i);
			}else if(i.getSemantic().equals("INV_BIND_MATRIX")){
				invBindmatrixSource = new ColladaDataSource<Matrix4>(skin, i);
			}
		}
		
		for(int i=0;i<jointIds.getCount();i++){
			joints.add(new Joint(jointIds.get(i),invBindmatrixSource.get(i)));
		}
		
		//Load weights
		int jointIdxOffset = -1;
		int weightIdxOffset = -1;
		ColladaDataSource<Float> weightSource = null;
		
		for(InputLocalOffset i:skin.getVertexWeights().getInputArray()){
			if(i.getSemantic().equals("JOINT")){
				jointIdxOffset = i.getOffset().intValue();
			}else if(i.getSemantic().equals("WEIGHT")){
				weightIdxOffset = i.getOffset().intValue();
				weightSource = new ColladaDataSource<Float>(skin, i);
			}
		}
		
		int offset = 0;
		List<?> vcount = skin.getVertexWeights().getVcount();
		List<?> pairs = skin.getVertexWeights().getV();
		
		vertexWeightJoints = new Joint[pairs.size()/2];
		vertexWeights = new float[vertexWeightJoints.length];
		vertexWeightsOffset = new int[skin.getVertexWeights().getCount().intValue()];
		
		for(int i=0;i<vertexWeightsOffset.length;i++){
			vertexWeightsOffset[i] = offset;
			
			int weightCount = ((BigInteger)vcount.get(i)).intValue();
			float sum=0;
			
			for(int j=0;j<weightCount;j++){
				int idx = ((Long)pairs.get((offset)*2+jointIdxOffset)).intValue();
				vertexWeightJoints[vertexWeightsOffset[i]+j] = joints.get(idx);
				sum += vertexWeights[vertexWeightsOffset[i]+j] = weightSource.get(((Long)pairs.get((offset)*2+weightIdxOffset)).intValue());
				
				offset++;
			}
			
			//normalize 
			for(int j=0;j<weightCount;j++){
				vertexWeights[vertexWeightsOffset[i]+j] /= sum;
			}
		}
	}
	
	static long initTime;
	static{
		initTime = System.nanoTime();
	}
	
	private void updateSkeleton(){
		//double secs = (System.nanoTime() - initTime)/1e9;
		
		
	}
	
	public void render(EngineObject observer) {
		updateSkeleton();
		
		geometry.render(observer);
	}
	
	private static Controller loadController(COLLADADocument doc, String url){
		String name = url.substring(1);
		
		for(LibraryControllers lc : doc.getCOLLADA().getLibraryControllersArray()){
			for(Controller c : lc.getControllerArray()){
				if(c.getId().equals(name))
					return c;
			}
		}
		
		throw new RuntimeException("Could not load Controller "+url);
	}
	
	private static ColladaNode[] searchSkeletonRoot(COLLADADocument doc, String[] urls){
		String[] nodeNames = new String[urls.length];
		ColladaNode[] ret = new ColladaNode[urls.length];
		
		urlsearch:
		for(int i=0;i<urls.length;i++){
			nodeNames[i] = urls[i].substring(1);

			for(LibraryVisualScenes lvs : doc.getCOLLADA().getLibraryVisualScenesArray())
				for(VisualScene vs : lvs.getVisualSceneArray())
					for(Node n : vs.getNodeArray())
						if(n.getId().equals(nodeNames[i])){
							ret[i] = ColladaNode.loadNode(doc,n);
							continue urlsearch;
						}else{
							Node r = searchNodeRec(n,nodeNames[i]);
							if(r!=null){
								ret[i] = ColladaNode.loadNode(doc,r);
								continue urlsearch;
							}
						}
		
			throw new RuntimeException("could not resolve skeleton root "+urls[i]);
		}
		
		return ret;
	}
	
	private static Node searchNodeRec(Node n, String id){
		if(n.getId().equals(id))
			return n;
		
		for(Node nn : n.getNodeArray()){
			Node r = searchNodeRec(nn, id);
			if(r!=null)
				return r;
		}
		
		return null;
	}
}

class Joint{
	Matrix4 invBindMatrix;
	
	public Joint(String id, Matrix4 invBindMatrix){
		this.invBindMatrix = invBindMatrix;
	}
}

